﻿/*
This file came from Managed Media Aggregation, You can always find the latest version @ https://net7mma.codeplex.com/
  
 Julius.Friedman@gmail.com / (SR. Software Engineer ASTI Transportation Inc. http://www.asti-trans.com)

Permission is hereby granted, free of charge, 
 * to any person obtaining a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, 
 * including without limitation the rights to :
 * use, 
 * copy, 
 * modify, 
 * merge, 
 * publish, 
 * distribute, 
 * sublicense, 
 * and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * 
 * JuliusFriedman@gmail.com should be contacted for further details.

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 * TORT OR OTHERWISE, 
 * ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * v//
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Java.Lang.Ref;
using Media.Common.Classes.Text;
using Media.Common.Extensions;
using Media.Rtcp;
using Media.Rtp;
using Media.Rtp.Rtcp;

namespace Media.RtpTools
{
    public class RtpSend
    {

        #region Reference

        /* 
         http://www.cs.columbia.edu/irt/software/rtptools/#rtpsend
         
         rtpsend sends an RTP packet stream with configurable parameters. This is intended to test RTP features. The RTP or RTCP headers are read from a file, generated by hand, a test program or rtpdump (format "ascii").
           
         The file file contains the description of the packets to be sent. Within the file, each entry starts with a time value, in seconds, relative to the beginning of the trace. The time value must appear at the beginning of a line, without white space. Within an RTP or RTCP packet description, parameters may appear in any order, without white space around the equal sign. Lines are continued with initial white space on the next line. Comment lines start with #. Strings are enclosed in quotation marks.
          
         The format is as follows: (
         
             <time> RTP 
               v=<version>
               p=<padding>
               x=<extension>
               m=<marker>
               pt=<payload type>
               ts=<time stamp>
               seq=<sequence number>
               ssrc=<SSRC>
               cc=<CSRC count>
               csrc=<CSRC>
               data=<hex payload>
               ext_type=<type of extension>
               ext_len=<length of extension header>
               ext_data=<hex extension data>
               len=<packet size in bytes(including header)>
            <time> RTCP (SDES v=<version> 
                          (src=<source> cname="..." name="...")
                          (src=<source> ...)
                        )
                        (SR v=<version>
                          ssrc=<SSRC of data source>
                          p=<padding>
                          count=<number of sources>
                          len=<length>
                          ntp=<NTP timestamp>
                          psent=<packet sent>
                          osent=<octets sent>
                            (ssrc=<SSRC of source>
                             fraction=<loss fraction>
                             lost=<number lost>
                             last_seq=<last sequence number>
                             jit=<jitter>
                             lsr=<last SR received>
                             dlsr=<delay since last SR>
                            ) 
                        )
             */

        #endregion

        /// <summary>
        /// String which are used in the format. See reference above.
        /// If this array is changed in order, e.g. entries are added or removed the rest of the parsing code will also need to be updated to reflect those changes.
        /// </summary>
        /// <remarks>See the <see cref="Reference"/> at the top of the file</remarks>
        internal static string[] Tokens = new[]
        {
       /*0*/"=", // assignment of a value occuring
            "RTP",            
            "v",//=<version>
            "p",//=<padding>
            "x",//=<extension>
            "m",//=<marker>
            "pt",//=<payload type>
            "ts",//=<time stamp>
            "seq",//=<sequence number>
            "ssrc",//ssrc=<SSRC>
            "cc",//=<CSRC count>
            "csrc",//=<CSRC>
            "data",//=<hex payload>
            "ext_type",//=<type of extension>
            "ext_len",//=<length of extension header>
            "ext_data",//=<hex extension data>
            "len",//=<packet size in bytes(including header)>
            //-------
        /*17*/"RTCP",
            "(",// Indicates a expression of information is present (may not be a token)
            "SDES",//slightly different format from others.
       /*(*/"src",//=<source> cname="..." name="...")
            @"""",// (in C# is literally a single quote)
            //-------
      /*22*/"SR",
            "RR",
            "SDES",
            "APP",
            "BYE",
            //pt = 0xPT for unknown RTCP types
            //ssrc
            "count",//=<number of sources>
            "ntp",//=<NTP timestamp>
            "psent",//=<packet sent>
            "osent",//=<octets sent>
               //(ssrc=<SSRC of source>
                 "fraction",//=<loss fraction>
                 "lost",//=<number lost>
                 "last_seq",//=<last sequence number>
                 "jit",//=<jitter>
                 "lsr",//=<last SR received>
                 "dlsr",//=<delay since last SR>
           /*37*/")",// Ends a previous expression (may not be a token)
                "from",//Not really part of the format
                 "\n", //Not really part of the format
        };

        #region Format Strings

        /// <summary>
        /// 0 is timeoffset , 1 is PacketFormat
        /// </summary>
        public const string Format = "{0} {1}",

        /// <summary>
            /// Used only for FileFormat.Short
            /// 0 = timeoffset, 1 is sequence number (where - indicates a marker bit), 2 is the Port
            /// </summary>
        ShortFormat = "{0} {1} {2}",

        /// <summary>
            /// The format '('String','Int','Int')' e.g. (IDVI,1,8000).
            /// Provided by a <see cref="PayloadDescription"/> usually obtained from
            /// <see cref="PayloadDescriptions"/> or <see cref="PayloadDescription.IsDynamic"/> or <see cref="PayloadDescription.IsUnknown"/>
            /// </summary>
        RtpPacketFormat = "RTP len={0} from={1}\nv={2}\np={3}\nx={4}\ncc={5}\nm={6}\npt={7}\n#{8}\nseq={9}\nssrc=0x{10:X2}\nts={11}\n",

        PayloadDescriptionFormat = "({0},{1})",

        RtcpPacketFormat = "RTCP len={0} from={1}\n",

        ExpressionFormat = "({0})", // Tokens[18] + Format_0 + Tokens[35], //

        RtcpExpressionFormat = "({0} ssrc=0x{1:X2} p={2} count={3} len={4}\n{5})",

        RtcpSendersInformationFormat = "ts={0}\n ntp={1}\n psent={2}\n osent={3}\n",

        RtcpReportBlockFormat = "(ssrc=0x{0:X2} fraction={1} lost={2} last_seq={3}\n jit={4} lsr={5} dlsr={6})\n",

        SourceDescriptionChunkFormat = "src=0x{0:X2} {1}",

        //E.g NAME="Something"
        QuotedFormat = "{0}=\"{1}\"",

        //E.g. v=2
        NonQuotedFormat = "{0}={1}",

        //Allow other specifiers?
        HexFormat = "{0}={1}{2}",

        UnknownSpecifier = "#unknown",

        HexSpecifier = "0x",

        NullSpecifier = "#nil",

        /// <summary>
        /// Anything goes Such that => Format_2 = Format_1 + Format_0, etc..
        /// </summary>
        Format_0 = "{0}",
        ///
        Format_1 = Format_0 + "{1}";

        #endregion

        #region PayloadDescription

        /// <summary>
        /// Used to idenfity and describe the encoding / FourCC of known media types.
        /// </summary>
        internal struct PayloadDescription
        {

            internal static bool IsDynamic(byte payloadType) { return payloadType >= 96 && payloadType <= 127; }

            //IsReserved?

            internal const string PayloadDescriptionFormat = "({0},{1},{2})";

            /// <summary>
            /// Used when no PayloadDescription is known this description is used.
            /// </summary>
            public static PayloadDescription Unknown = new PayloadDescription()
            {
                EncodingName = "Unknown",
                Clockrate = -1,
                PayloadType = 255,
                Channel = 255
            };

            /// <summary>
            /// Used for dynamic unknown formats
            /// </summary>
            public static PayloadDescription Dynamic = new PayloadDescription()
            {
                EncodingName = "Dynamic",
                Clockrate = -1,
                PayloadType = 96,
                Channel = 0 //1
            };

            public static PayloadDescription Reverved = new PayloadDescription()
            {
                EncodingName = "Reserved",
                Clockrate = -1,
                PayloadType = 0,
                Channel = 0 //1
            };

            public static PayloadDescription Unassigned = new PayloadDescription()
            {
                EncodingName = "Unassigned",
                Clockrate = -1,
                PayloadType = 0,
                Channel = 0 //1
            };

            public static PayloadDescription ConflictAvoidance = new PayloadDescription()
            {
                EncodingName = "RTCP conflict avoidance (Reserved)",
                Clockrate = -1,
                PayloadType = 0,
                Channel = 0 //1
            };

            /// <summary>
            /// Also possibly the <see href="http://en.wikipedia.org/wiki/FourCC">FourceCC</see>
            /// </summary>
            public string EncodingName { get; internal set; }

            /// <summary>
            /// The value expected in the <see cref="RtpPacket.PayloadType"/> field for the description.
            /// </summary>
            public byte PayloadType { get; internal set; }

            /// <summary>
            /// Of the underlying media description
            /// </summary>
            public int Clockrate { get; internal set; }

            /// <summary>
            /// Used traditionally to describe the Rtp 1.0 channel field in which the data was expected to be recieved.
            /// <see cref="ftp://gaia.cs.umass.edu/pub/hgschulz/rtp/draft-ietf-avt-rtp-04.txt"/>
            /// </summary>
            /*
             V V P X C C C C M T T T T T T T S S S S S S S S S S S S S S S S (RTP 2 Header Comparison)
             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |Ver| ChannelID |P|S|  format   |       sequence number         |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |     timestamp (seconds)       |     timestamp (fraction)      |
            +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
            | options ...                                                   |
            +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
             */

            public byte Channel { get; internal set; }

            public override string ToString()
            {
                return string.Format(PayloadDescriptionFormat, EncodingName, Channel, Clockrate);
            }
        }

        /// <summary>
        /// Used internally, should possibly allow for registration and expansion.
        /// </summary>
        static internal Dictionary<byte, PayloadDescription> PayloadDescriptions = new Dictionary<byte, PayloadDescription>()
        {
            {0, new PayloadDescription() {EncodingName = "PCMU", Clockrate = 8000, Channel = 1, PayloadType = 0 } },
            {1, new PayloadDescription() {EncodingName = "1016", Clockrate = 8000, Channel = 1, PayloadType = 1 } },
            {2, new PayloadDescription() {EncodingName = "G721 ", Clockrate = 8000, Channel = 1, PayloadType = 2 } },
            {3, new PayloadDescription() {EncodingName = "GSM", Clockrate = 8000, Channel = 1, PayloadType = 3 } },
            {4, new PayloadDescription() {EncodingName = "G723", Clockrate = 8000, Channel = 1, PayloadType = 4 } },
            {5, new PayloadDescription() {EncodingName = "DVI4", Clockrate = 8000, Channel = 1, PayloadType = 5 } },
            {6, new PayloadDescription() {EncodingName = "DVI4 ", Clockrate = 16000, Channel = 1, PayloadType = 6 } },
            {7, new PayloadDescription() {EncodingName = "LPC", Clockrate = 8000, Channel = 1, PayloadType = 7 } }, 
            {8, new PayloadDescription() {EncodingName = "PCMA", Clockrate = 8000, Channel = 1, PayloadType = 8 } },
            {9, new PayloadDescription() {EncodingName = "G722", Clockrate = 8000, Channel = 1, PayloadType = 9 } },
            {10, new PayloadDescription() {EncodingName = "L16", Clockrate = 44100, Channel = 2, PayloadType = 10 } },
            {11, new PayloadDescription() {EncodingName = "L16", Clockrate = 44100, Channel = 1, PayloadType = 11 } },
            {12, new PayloadDescription() {EncodingName = "QCELP", Clockrate = 8000, Channel = 1, PayloadType = 12 } },
            {13, new PayloadDescription() {EncodingName = "ComfortNoise", Clockrate = 8000, Channel = 0, PayloadType = 13 } },
            {14, new PayloadDescription() {EncodingName = "MPA", Clockrate = 90000, Channel = 0, PayloadType = 14 } },
            {15, new PayloadDescription() {EncodingName = "G728", Clockrate = 8000, Channel = 1, PayloadType = 15 } },
            {16, new PayloadDescription() {EncodingName = "DVI4 ", Clockrate = 11025, Channel = 1, PayloadType = 16 } },
            {17, new PayloadDescription() {EncodingName = "DVI4", Clockrate = 22050, Channel = 1, PayloadType = 17 } },
            {18, new PayloadDescription() {EncodingName = "G729", Clockrate = 8000, Channel = 1, PayloadType = 18 } },
            {19, new PayloadDescription() {EncodingName = "ComfortNoise", Clockrate = 8000, Channel = 0, PayloadType = 19 } },
            {20, RtpSend.PayloadDescription.Reverved},
            {21, RtpSend.PayloadDescription.Reverved},
            {22, RtpSend.PayloadDescription.Reverved},            
            {23, new PayloadDescription() {EncodingName = "SCR", Clockrate = 90000, Channel = 0, PayloadType = 23 } },
            {24, new PayloadDescription() {EncodingName = "MPEG", Clockrate = 90000, Channel = 0, PayloadType = 24 } },
            {25, new PayloadDescription() {EncodingName = "CelB", Clockrate = 90000, Channel = 0, PayloadType = 25 } },
            {26, new PayloadDescription() {EncodingName = "JPEG", Clockrate = 90000, Channel = 0, PayloadType = 26 } },
            {27, new PayloadDescription() {EncodingName = "CUSM", Clockrate = 90000, Channel = 0, PayloadType = 27 } },
            {28, new PayloadDescription() {EncodingName = "NV", Clockrate = 90000, Channel = 0, PayloadType = 28 } },
            {29, new PayloadDescription() {EncodingName = "PicW", Clockrate = 90000, Channel = 0, PayloadType = 29 } },
            {30, new PayloadDescription() {EncodingName = "CPV", Clockrate = 90000, Channel = 0, PayloadType = 30 } },
            {31, new PayloadDescription() {EncodingName = "H261", Clockrate = 90000, Channel = 0, PayloadType = 31 } },
            {32, new PayloadDescription() {EncodingName = "MPV", Clockrate = 90000, Channel = 0, PayloadType = 32 } },
            {33, new PayloadDescription() {EncodingName = "MP2T", Clockrate = 90000, Channel = 0, PayloadType = 33 } },
            {34, new PayloadDescription() {EncodingName = "H263", Clockrate = 90000, Channel = 0, PayloadType = 34 } },
            //35 - 71 unassigned.
            //72-76 Reserved for RTCP conflict avoidance	
            //77 - 95 Unassigned
            //96 - 127 Dynamic
        };

        #endregion

        /// <summary>
        /// Prepares the text in the stream which corresponds to the RtcpData of a Rtcp.RtcpPacket.
        /// </summary>
        /// <param name="format">The <see cref="FileFormat"/> to output.</param>
        /// <param name="packet">The <see cref="Rtcp.RtcpPacket"/> to describe</param>
        /// <returns>The text describes the packet if the <paramref name="format"/> is a text format, otherwise an empty string</returns>
        public static string ToTextualConvention(FileFormat format, Rtcp.RtcpPacket packet)
        {
            if (packet == null || packet.Payload.Count == 0 || format < FileFormat.Text || format == FileFormat.Short) return string.Empty;

            if(format == FileFormat.Unknown) return UnknownSpecifier;
            
            //Determine the format to use as well as the `Conventional Abbreviation`
            switch (packet.PayloadType) 
            {
                case Rtcp.SourceDescriptionReport.PayloadType:
                    //Use a SourceDescriptionReport to enumerate the SourceDescriptionChunk's and SourceDescriptionItem's contained in the packet.
                    using (var sdes = new Rtcp.SourceDescriptionReport(packet, false)) //Don't dispose the packet when done.
                    {
                        return string.Format(RtcpExpressionFormat,
                            "SDES",
                            packet.SynchronizationSourceIdentifier,
                            packet.Padding ? 1.ToString() : 0.ToString(),
                            packet.BlockCount,
                            packet.Header.LengthInWordsMinusOne, ToTextualConvention(sdes));
                    }
                case Rtcp.SendersReport.PayloadType:
                    using (var sr = new Rtcp.SendersReport(packet, false))
                    {
                        return string.Format(RtcpExpressionFormat,
                            "SR",
                            packet.SynchronizationSourceIdentifier,
                            packet.Padding ? 1.ToString() : 0.ToString(),
                            packet.BlockCount,
                            packet.Header.LengthInWordsMinusOne,
                            (ToTextualConvention(sr) + (char)ASCII.Space + string.Format(RtcpSendersInformationFormat,
                            //0
                                (DateTime.UtcNow - sr.NtpDateTime).TotalSeconds.ToString("0.000000"), //ts=
                            //1
                                sr.NtpTimestamp, //ntp=
                            //2
                                sr.SendersOctetCount, //osent=
                            //3
                                sr.SendersPacketCount))); //psent=
                    }
                case Rtcp.ReceiversReport.PayloadType:                    
                    using (var rr = new Rtcp.ReceiversReport(packet, false))
                    {
                        return string.Format(RtcpExpressionFormat,
                            "RR",
                            packet.SynchronizationSourceIdentifier,
                            packet.Padding ? 1.ToString() : 0.ToString(),
                            packet.BlockCount,
                            packet.Header.LengthInWordsMinusOne, 
                            ToTextualConvention(rr));
                    }
                case Rtcp.GoodbyeReport.PayloadType:                    
                    using (var bye = new Rtcp.GoodbyeReport(packet, false)) 
                    {
                        return string.Format(RtcpExpressionFormat,
                            "BYE",
                            packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(),
                            packet.BlockCount,
                            packet.Header.LengthInWordsMinusOne, ToTextualConvention(bye));
                    }
                case Rtcp.ApplicationSpecificReport.PayloadType:
                    return string.Format(RtcpExpressionFormat, "APP", packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(), packet.BlockCount, packet.Header.LengthInWordsMinusOne, string.Empty);
                default:
                    //Unknown PayloadType use a Hex Representation of the PayloadType
                    return string.Format(RtcpExpressionFormat, packet.PayloadType.ToString("X"), packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(), packet.BlockCount, packet.Header.LengthInWordsMinusOne, string.Empty);
            }
        }

        /// <summary>
        /// Internal use only, provides a `rtpsend` compatible text description of the given report blocks.
        /// </summary>
        /// <param name="reportBlocks">The blocks to describe</param>
        /// <returns>The string which describes the given blocks.</returns>
        internal static string ToTextualConvention(IEnumerable<IReportBlock> reportBlocks) 
        {
            string blockString = string.Empty;

            if (reportBlocks == null || !reportBlocks.Any()) return blockString;            

            //Build the block string for each block in the report
            foreach (Rtcp.ReportBlock reportBlock in reportBlocks)
                blockString += (char)ASCII.Space + string.Format(RtcpReportBlockFormat, 
                    reportBlock.SendersSynchronizationSourceIdentifier, 
                    reportBlock.FractionsLost, 
                    reportBlock.CumulativePacketsLost, 
                    reportBlock.ExtendedHighestSequenceNumberReceived, 
                    reportBlock.InterarrivalJitterEstimate, 
                    reportBlock.LastSendersReportTimestamp, 
                    reportBlock.DelaySinceLastSendersReport);

            return blockString;
        }

        /// <summary>
        /// Internal use only provides a `rtpsend` compatible text description of a <see cref="SourceDescriptionReport"/>.
        /// </summary>
        /// <param name="sdes">The <see cref="SourceDescriptionReport"/> to describe</param>
        /// <returns>The string</returns>
        internal static string ToTextualConvention(Rtcp.SourceDescriptionReport sdes)
        {
            if (sdes == null || sdes.IsDisposed) return string.Empty;

            StringBuilder blockStringBuilder = new StringBuilder(sdes.BlockCount * Rtcp.SourceDescriptionReport.SourceDescriptionItem.ItemHeaderSize);

            //The blockString is formatted per chunk of the sdes
            foreach (Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk chunk in sdes)
            {
                //A SDES packet description requires an item sub description in `QuotedFormat`
                StringBuilder itemStringBuilder = new StringBuilder();

                int countMinus1 = -1;

                //For each item in the chunk build up the item string
                foreach (Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem item in chunk)
                {
                    //Add Whitespace delimiter
                    if (++countMinus1 > 0) itemStringBuilder.Append((char)ASCII.Space);

                    string typedName = Enum.GetName(typeof(Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType), item.ItemType);

                    if (string.IsNullOrWhiteSpace(typedName)) typedName = StringExtensions.UnknownString;

                    itemStringBuilder.AppendFormat(QuotedFormat,
                        //0
                        typedName,
                        //1
                        Encoding.ASCII.GetString(item.ItemData.ToArray()));
                }

                blockStringBuilder.Append((char)ASCII.LineFeed);

                blockStringBuilder.AppendFormat(SourceDescriptionChunkFormat,
                    chunk.ChunkIdentifer,
                    itemStringBuilder.ToString());
            }

            return blockStringBuilder.ToString();
        }

        internal static string ToTextualConvention(Rtcp.GoodbyeReport bye) 
        {
            string blockString = string.Empty;

            if (bye == null || bye.IsDisposed) return blockString;

            //if HasReasonForLeaving add
            if (bye.HasReasonForLeaving)//reason=
                blockString += string.Format(QuotedFormat, "reason", System.Text.Encoding.ASCII.GetString(bye.ReasonForLeavingData.ToArray()));

            //Write each entry in bye.GetSourceList
            using (var sourceList = bye.GetSourceList())
            {
                if (sourceList == null) blockString += "#Incomplete Source List Not Included" + (char)ASCII.LineFeed;
                else foreach (uint partyId in sourceList)//ssrc=
                        blockString += string.Format(HexFormat, "ssrc", HexSpecifier + partyId.ToString("X"), (char)ASCII.LineFeed);
            }

            return blockString;
        }

        /// <summary>
        /// Prepares a text description which is compatible with 'rtpsend' among other 'rtp tools'.
        /// </summary>
        /// <param name="format">The <see cref="FileFormat"/> to output.</param>
        /// <param name="packets">The <see cref="Rtcp.RtcpPacket"/>'s to describe</param>
        /// <param name="time">Timeoffset from when recoding began, should be 0 for the first packet.</param>
        /// <param name="source">The <see cref="System.Net.IPEndPoint"/> from which the packet was received.</param>
        /// <returns>The text which describes the packet if the <paramref name="format"/> is a text format, otherwise an empty string</returns>
        public static string ToTextualConvention(FileFormat format, IEnumerable<Rtcp.RtcpPacket> packets, TimeSpan time, System.Net.IPEndPoint source)
        {
            //Short form does not allow Rtcp, Binary Formats have no textual convention.
            if (packets == null || format == FileFormat.Short || format <= FileFormat.Dump) return string.Empty;

            //Store the hex payload in one builder
            StringBuilder hexPayload = format == FileFormat.Hex ? new StringBuilder(64) : null;

            //And the overall result in another
            StringBuilder builder = new StringBuilder(64);

            int totalLength = 0;

            foreach (Rtcp.RtcpPacket packet in packets)
            {
                //Build the Expression of the packet given as an individual expression
                //(T=>)
                builder.Append(RtpSend.ToTextualConvention(format, packet));

                //Increment for totalLength
                totalLength += packet.Length;

                if (format == FileFormat.Hex)
                    hexPayload.Append(BitConverter.ToString(packet.Payload.ToArray()).Replace("-", string.Empty));
            }

            //Insert the header
            builder.Insert(0, string.Format(RtpSend.Format,
                //0
                time.TotalSeconds.ToString("0.000000"),
                //1
                string.Format(RtpSend.RtcpPacketFormat,
                //0
                    totalLength,
                //1
                    source.Address.ToString() + ':' + source.Port.ToString())));

            //hex dump
            if (format == FileFormat.Hex)
                if (totalLength > 0) builder.Append(string.Format(HexFormat, "data", hexPayload, (char)ASCII.LineFeed));
                else builder.Append(string.Format(HexFormat, "data", NullSpecifier, (char)ASCII.LineFeed));

            //Return the allocated result
            return builder.ToString();
        }

        /// <summary>
        /// Prepares a text description which is compatible with 'rtpsend' among other 'rtp tools'.
        /// </summary>
        /// <param name="format">The <see cref="FileFormat"/> to output.</param>
        /// <param name="packet">The <see cref="Rtp.RtpPacket"/> to describe</param>
        /// <param name="time">Timeoffset from when recoding began, should be 0 for the first packet.</param>
        /// <param name="source">The <see cref="System.Net.IPEndPoint"/> from which the packet was received.</param>
        /// <returns>The text which describes the packet if the <paramref name="format"/> is a text format, otherwise an empty string</returns>
        public static string ToTextualConvention(FileFormat format, Rtp.RtpPacket packet, TimeSpan time, System.Net.IPEndPoint source)
        {
            //StringBuilder?

            //If the format is Short (Vat or Rtp Data in tablular form)
            if (format == FileFormat.Short)
            {
                //Then return that format
                return string.Format(RtpSend.ShortFormat,
                    //0
                    time.TotalSeconds.ToString("0.000000"),
                    //1
                    (packet.Marker ? (-packet.SequenceNumber) : packet.SequenceNumber).ToString(),
                    //2
                    source.Port.ToString());
            }

            StringBuilder builder = new StringBuilder(64);

            //All Rtp are described with this format
            builder.Append(string.Format(RtpSend.Format, 
                //0
                time.TotalSeconds.ToString("0.000000"),
                //1
                string.Format(RtpSend.RtpPacketFormat,
                    //0
                    packet.Length, //Packet size in bytes including header
                    //1
                    source.Address.ToString() + ':' + source.Port.ToString(),
                    //2
                    packet.Version.ToString(),
                    //3
                    packet.Padding ? "1" : "0",
                    //4
                    packet.Extension ? "1" : "0",
                    //5
                    packet.ContributingSourceCount.ToString(),
                    //6
                    packet.Marker ? "1" : "0",
                    //7
                    packet.PayloadType,
                    //8 (Format description)
                    string.Format(RtpSend.PayloadDescriptionFormat, packet.PayloadType, RtpSendExtensions.PayloadDescription(packet)),
                    //9
                    packet.SequenceNumber,
                    //10
                    packet.SynchronizationSourceIdentifier,
                    //11
                    packet.Timestamp                )));

            //Write the textual representation of the items in the sourceList
            if (packet.ContributingSourceCount > 0)
            {

                //Note
                //http://www.cs.columbia.edu/irt/software/rtptools/ChangeLog.html
                //States the format is not hex when in the format csrc[n]
                //However - 
                //http://www.cs.columbia.edu/irt/software/rtptools/#rtpsend
                //States that the format is :
                //cc=<CSRC count>
                //csrc=<CSRC>
                //This is basically the same thing and a parsing semantic.

                using (Media.RFC3550.SourceList sl = new Media.RFC3550.SourceList(packet))
                {
                    {
                        //csrc=
                        while (sl.MoveNext())
                        {
                            builder.Append(string.Format(RtpSend.HexFormat, "csrc", HexSpecifier, sl.CurrentSource.ToString("X"), (char)ASCII.LineFeed));
                        }
                    }
                }
            }

            //Write the textual representation of the Extension
            if (packet.Extension)
            {
                using (var rtpExtension = packet.GetExtension())
                {

                    if (rtpExtension == null)
                    {
                        builder.Append("#Incomplete Extension Not Included");
                        builder.Append((char)ASCII.LineFeed);
                    }
                    else
                    {
                        builder.Append(string.Format(RtpSend.HexFormat, "ext_type", HexSpecifier + rtpExtension.Flags.ToString("X"), (char)ASCII.LineFeed));

                        builder.Append(string.Format(RtpSend.NonQuotedFormat, "ext_len", rtpExtension.LengthInWords));
                        builder.Append((char)ASCII.LineFeed);

                        builder.Append(string.Format(RtpSend.HexFormat, "ext_data",  BitConverter.ToString(rtpExtension.Data.ToArray()).Replace("-", string.Empty), (char)ASCII.LineFeed));
                    }
                }
            }

            //If the format is hex then add the payload dump
            if (format == FileFormat.Hex)
            {
                var data = packet.PayloadData;
                if (data.Any()) builder.Append(string.Format(RtpSend.HexFormat, "data", BitConverter.ToString(data.ToArray()).Replace("-", string.Empty), (char)ASCII.LineFeed));
                else builder.Append(string.Format(HexFormat, "data", NullSpecifier, (char)ASCII.LineFeed));
            }

            //Return the result
            return builder.ToString();
        }       
       
        //Move to RtpSendExtensions?

        /// <summary>
        /// Parses the data contained in the given <see cref="System.IO.BinaryReader"/> for data which corresponds to a format compatible with <see href="http://www.cs.columbia.edu/irt/software/rtptools/#rtpsend">rtpsend</see>.
        /// If a Binary format is encoutered (by the presence of the "#!rtpplay1.0" file header then <see cref="RtpTools.RtpDump.RtpDumpExtensions.ReadBinaryToolEntry"/> will be called implicitly with the reader given,
        /// In such a case, unexpected will contain the data which matched the file header.
        /// </summary>
        /// <param name="reader">The <see cref="System.IO.BinaryReader"/> which should be created using <see cref="System.Text.Encoding.ASCII"/></param>
        /// <param name="format">The format found while parsing the description.</param>
        /// <param name="unexpected">Will only contain data if format was unknown, and contains the data encountered in the stream while attempting to parse.</param>
        /// <returns>The item which was created as a result of reading from the stream.</returns>
        internal static RtpToolEntry ParseText(System.IO.BinaryReader reader, System.Net.IPEndPoint source, ref FileFormat format, out byte[] unexpected)
        {
            unexpected = null;

            double timeOffset = 0;

            System.Net.IPEndPoint sourceInfo = source;

            long position = reader.BaseStream.Position;

            Common.IPacket builtPacket = null;

            //Keep track of making a Rtp or Rtcp entry.
            bool rtp = false;

            Rtp.RtpPacket rtpP = null;

            Rtcp.RtcpPacket rtcP = null;

            ///<summary>
            /// each entry starts with a time value, in seconds, relative to the beginning of the trace. 
            /// The time value must appear at the beginning of a line, without white space. Within an RTP or RTCP packet description, 
            /// parameters may appear in any order, without white space around the equal sign. 
            /// Lines are continued with initial white space on the next line. 
            /// Comment lines start with #. Strings are enclosed in quotation marks.
            /// <see cref="Tokens"/>
            ///</summary>

            //The amount of tokens consumed from the reader, where a token is defined as above
            int tokensParsed = -1,

            //The amount of bytes read
            lineBytesLength = 0,

            //Used for token parsing, the index of the recognized token in 'Tokens'
            tokenIndex = -1;

            //Indicates if in a comment
            bool parsingCommentOrWhitespace = false;

            //Contains the data read from the stream until '\n' occurs.
            byte[] lineBytes;

            //Indicates if the parsing of the entry is complete
            bool doneParsing = false, formatUnknown = format == FileFormat.Unknown, needAnyToken = true;

            //A string instance which was used to compare to known `Tokens`
            string token;

            //No bytes have actually been consumed from the stream yet, while not done parsing and not at the end of the stream
            while (!doneParsing && reader.BaseStream.Position < reader.BaseStream.Length)
            {
                //Determine the following character (ASCIIEncoding SHOULD have been specified in creation of the reader) [If not could possibly have also found out without consuming a byte here]
                int peek = reader.PeekChar();

                //If no data can be read
                if (peek == -1)
                {
                    //then indiate an unknown format and then return null
                    format = FileFormat.Unknown;
                    return null;
                }
                else if (peek == RtpDump.RtpDumpExtensions.Hash || peek == (char)ASCII.Space)
                {
                    //Comment lines start with # (Hash). Strings are enclosed in quotation marks.
                    parsingCommentOrWhitespace = true;

                    //Could be a binary format however....
                }
                else if (tokensParsed > 0 && peek == 'r' || peek == 'R') //Don't read any further a new entry follows (Could be a malformed entry with rXXX=YYY\n)
                {
                    //doneParsing = true;
                    break;
                }

                //Read until '\n' occurs
                RtpSendExtensions.ReadLineFeed(reader.BaseStream, out lineBytes);

                //Keep track of the amount of bytes read
                lineBytesLength = lineBytes.Length;

                //If nothing was read return
                if (lineBytesLength == 0) return null;

                //If the format is unknown then 
                if (formatUnknown)
                {
                    //check for the Binary format at the known ordinal, if found
                    if (lineBytesLength > 2 && lineBytes[0] == RtpDump.RtpDumpExtensions.Hash && lineBytes[1] == RtpDump.RtpDumpExtensions.Bang)
                    {
                        //Indicate a binary format so far
                        format = FileFormat.Binary;

                        //give the `unexpected` data back to the caller, which consisted of the header
                        unexpected = lineBytes;

                        //Remove the reference to the token now
                        token = null;

                        //Return the result of reading the binary entry.
                        return null;
                    }

                    //Check for the short form before parsing a token

                    //Search for '='

                    tokenIndex = Array.IndexOf<byte>(lineBytes, ASCII.EqualsSign);

                    //If not found then this must be a Short entry.
                    if (tokenIndex == -1)
                    {
                        //No longer unknown because,
                        formatUnknown = false;

                        //This seems to be Short format
                        format = FileFormat.Short;
                    }
                    else //There was a '=' sign
                    {
                        //No longer still unknown because,
                        //This seems to be a Text format
                        format = FileFormat.Text;

                        //but we need to consume tokens until data in tokens as they occur to be sure
                    }
                }

                //If not found then this must be a Short entry.
                if (format == FileFormat.Short) return RtpToolEntry.CreateShortEntry(DateTime.UtcNow.Subtract(TimeSpan.FromMilliseconds(timeOffset)), sourceInfo, lineBytes, 0, position);
                else if (parsingCommentOrWhitespace)//If parsing a whitespace or a comment
                {
                    //Not parsing the comment / whitespace any more
                    parsingCommentOrWhitespace = false;

                    //Perform another iteration
                    continue;
                }
                else if (needAnyToken) //If a token is needed
                {

                    //Extract all tokens from the lineBytes
                    string[] tokens = Encoding.ASCII.GetString(lineBytes).Split((char)ASCII.Space, (char)ASCII.EqualsSign, '(', ')');

                    //Any hex data will need a length, and we start at offset 0.
                    int dataLen = 0, tokenOffset = 0;

                    //If nothing was tokenized then return the unexpected data.
                    if (tokens.Length == 0)
                    {
                        unexpected = lineBytes;
                        return null;
                    }

                    //The first token must be a timeOffset
                    if (timeOffset == 0) timeOffset = double.Parse(tokens[tokenOffset++]);

                    //For each token in the tokens after the timeOffset
                    for (int e = tokens.Length; tokenOffset < e; ++tokenOffset)
                    {
                        //Get the token at the index
                        token = tokens[tokenOffset];

                        //Determine what to do based on if there was a matching token in the Tokens array.
                        tokenIndex = Array.IndexOf<string>(Tokens, token);

                        //The entry must be finished.
                        if (-1 == tokenIndex)
                        {
                            unexpected = lineBytes;
                            break;
                        }

                        //Increment for a token parsed within the entry so far
                        ++tokensParsed; 

                        //Not increased for each token?

                        //Switch out logic based on token, could also just handle based on token and save useless lookup.
                        switch (tokenIndex)
                        {
                            //RTP
                            case 1:
                                {
                                    //The created structure will have a packetLength = 8 + dataLen
                                    rtp = true;

                                    rtpP = new Rtp.RtpPacket(0, false, false, Media.Common.MemorySegment.EmptyBytes);

                                    builtPacket = rtpP;

                                    //Do another iteration
                                    continue;
                                }
                            //RTCP
                            case 17:
                                {

                                    rtcP = new Rtcp.RtcpPacket(0, 0, 0, 0, 0, 0);

                                    //The created structure will have packetLen = 0 to indicate Rtcp.
                                    builtPacket = rtcP;

                                    //Do another iteration

                                    continue;
                                }
                            case 38: //from
                                {

                                    string[] parts = tokens[++tokenOffset].Split((char)ASCII.Colon);

                                    System.Net.IPAddress ip = System.Net.IPAddress.Parse(parts[0]);

                                    int port = int.Parse(parts[1]);

                                    System.Diagnostics.Debug.WriteLine(ip + " " + port);

                                    sourceInfo = new System.Net.IPEndPoint(ip, port);

                                    continue;
                                }
                            case 2:
                                {
                                    //version

                                    int version = int.Parse(tokens[++tokenOffset]);

                                    System.Diagnostics.Debug.WriteLine(version);


                                    if (rtp) rtpP.Header.Version = version;
                                    else rtcP.Header.Version = version;

                                    continue;
                                }
                            case 3: //p
                                {
                                    int paddingCount = int.Parse(tokens[++tokenOffset]);

                                    System.Diagnostics.Debug.WriteLine(paddingCount);

                                    break;
                                }
                            case 4: //x
                                {
                                    if (rtp)
                                    {
                                        bool hasExtension = int.Parse(tokens[++tokenOffset]) == 1;

                                        System.Diagnostics.Debug.WriteLine(hasExtension);

                                        rtpP.Header.Extension = hasExtension;
                                    }

                                    break;
                                }
                            case 5: //m
                                {
                                    if (rtp)
                                    {
                                        bool hasMarker = int.Parse(tokens[++tokenOffset]) == 1;

                                        System.Diagnostics.Debug.WriteLine(hasMarker);

                                        rtpP.Header.Marker = hasMarker;
                                    }
                                    break;
                                }
                            case 6: //pt
                                {

                                    int payloadType = int.Parse(tokens[++tokenOffset]);

                                    System.Diagnostics.Debug.WriteLine(payloadType);

                                    //Will not occur in Rtcp types or at least should not.

                                    if (rtp) rtpP.Header.PayloadType = payloadType;
                                    else rtcP.Header.PayloadType = payloadType;

                                    break;
                                }
                            case 7: //ts
                                {
                                    if (rtp)
                                    {
                                        int ts = int.Parse(tokens[++tokenOffset]);

                                        System.Diagnostics.Debug.WriteLine(ts);

                                        rtpP.Header.Timestamp = ts;
                                    }

                                    break;
                                }
                            case 8: //seq
                                {
                                    if (rtp)
                                    {
                                        int seq = int.Parse(tokens[++tokenOffset]);

                                        System.Diagnostics.Debug.WriteLine(seq);

                                        rtpP.Header.SequenceNumber = seq;
                                    }
                                    break;
                                }
                            case 9: //ssrc
                                {

                                    token = tokens[++tokenOffset];

                                    token = token.Remove(token.Length - 1).Replace(HexSpecifier, string.Empty);

                                    int ssrc = 0;

                                    if (!int.TryParse(token, out ssrc)) //plain int                        
                                        ssrc = int.Parse(token, System.Globalization.NumberStyles.HexNumber);//hex

                                    System.Diagnostics.Debug.WriteLine(ssrc);

                                    if (rtp) rtpP.Header.SynchronizationSourceIdentifier = ssrc;
                                    else rtcP.Header.SendersSynchronizationSourceIdentifier = ssrc;

                                    break;
                                }
                            case 10: //cc
                                {

                                    int cc = int.Parse(tokens[++tokenOffset]);

                                    System.Diagnostics.Debug.WriteLine(cc);

                                    if (rtp) rtpP.Header.ContributingSourceCount = cc;
                                    else rtcP.Header.BlockCount = cc;

                                    break;
                                }
                            case 11: //csrc
                                {

                                    int csrc = int.Parse(tokens[++tokenOffset]);

                                    System.Diagnostics.Debug.WriteLine(csrc);

                                    //Add to a list.

                                    break;
                                }
                            case 12://data
                                {
                                    //Token based reading may not be required anymore
                                    needAnyToken = false;

                                    //Not unknown anymore because,
                                    formatUnknown = false;

                                    //Definitely hex format
                                    format = FileFormat.Hex;

                                    //The next token is the string in hex format of the payload.
                                    ++tokenIndex;

                                    //If it begins with Hash then its NIL

                                    //Done parsing the entry.
                                    doneParsing = true;

                                    continue;
                                }
                            case 13: //ext_type
                                {

                                    int ext_type = int.Parse(tokens[++tokenOffset]);

                                    System.Diagnostics.Debug.WriteLine(ext_type);

                                    break;
                                }
                            case 14: //ext_len
                                {

                                    int ext_len = int.Parse(tokens[++tokenOffset]);

                                    System.Diagnostics.Debug.WriteLine(ext_len);

                                    break;
                                }
                            case 15: //ext_data
                                {

                                    //The next token is the string in hex format of the payload.

                                    ++tokenIndex;

                                    //If it begins with Hash then its NIL

                                    break;
                                }
                            case 16: //len
                                {
                                    dataLen = int.Parse(tokens[++tokenOffset]);

                                    System.Diagnostics.Debug.WriteLine(dataLen);

                                    continue;
                                }
                            //APP, BYE, RR, SR, SDES                            
                            default: //-1 and others
                                {
                                    //If the format was unknown
                                    if (formatUnknown)
                                    {
                                        //It is no longer so because,
                                        formatUnknown = false;

                                        //it is no longer unknown, it is definitely a Text format
                                        format = FileFormat.Text;
                                    }

                                    break;
                                }
                        }//Done determining what to do with a token
                    }//Done with tokens
                }//Don't need to parse any tokens

            }

            //Create the resulting entry with the data contained in memory read from the reader by the writer

            //http://net7mma.codeplex.com/workitem/17176
            //Fix to provide correct format here
            //kind of difficult because text formats are similar, would use tokens present to determine format.

            return new RtpToolEntry(DateTime.UtcNow.Subtract(TimeSpan.FromMilliseconds(timeOffset)), sourceInfo, builtPacket, (int)timeOffset, position);
            //{
            //    Format = FileFormat.Text
            //};
        }
        
        public class Program
        {
            public static void Main(string[] args)
            {

            }
        }

    }

    public static class RtpSendExtensions 
    {

        public static string PayloadDescription(this Rtp.RtpPacket packet)
        {
            //Check for a packet
            if (packet == null) throw new ArgumentNullException("packet");

            //Get the PayloadType from the header.
            byte payloadType =(byte)packet.PayloadType;

            //Check for dynamic first
            if (RtpSend.PayloadDescription.IsDynamic(payloadType)) return RtpSend.PayloadDescription.Dynamic.EncodingName;

            //Check for conflict avoidance
            if (payloadType >= 72 && payloadType <= 76) return RtpSend.PayloadDescription.ConflictAvoidance.EncodingName;

            //Check for conflict avoidance
            if (payloadType >= 35 && payloadType <= 71 || payloadType >= 77 && payloadType < RtpSend.PayloadDescription.Dynamic.PayloadType) return RtpSend.PayloadDescription.Unassigned.EncodingName;

            //Default to unknown if not found
            RtpSend.PayloadDescription description;
            if (false == RtpSend.PayloadDescriptions.TryGetValue((byte)packet.PayloadType, out description)) return RtpSend.PayloadDescription.Unknown.EncodingName;

            //Return the name as found
            return description.EncodingName;
        }

        /// <summary>
        /// Reads all bytes which occur in the stream until '\n' or the End of Stream occurs.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="result"></param>
        internal static void ReadLineFeed(System.IO.Stream stream, out byte[] result)
        {
            //The length of the array allocated is known and should also be returned...
            result = RtpDump.RtpDumpExtensions.ReadDelimitedValue(stream, ASCII.LineFeed, true);
        }
    }
}
